package org.hxzon.util.json;

//Copyright 2007, 2008, 2010, 2011, 2012 The Apache Software Foundation

//package org.apache.tapestry5.json;

/*
* Copyright (c) 2002 JSON.org
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* The Software shall be used for Good, not Evil.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
* A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with
* commas separating the values. The internal form is an object having {@code get} and {@code opt} methods for
* accessing the values by index, and {@code put} methods for adding or replacing values. The values can be any of
* these types: {@code Boolean}, {@code JSONArray}, {@code JSONObject}, {@code Number},
* {@code String}, or the {@code JSONObject.NULL object}.
* <p/>
* The constructor can convert a JSON text into a Java object. The {@code toString} method converts to JSON text.
* <p/>
* A {@code get} method returns a value if one can be found, and throws an exception if one cannot be found. An
* {@code opt} method returns a default value instead of throwing an exception, and so is useful for obtaining
* optional values.
* <p/>
* The generic {@code get()} and {@code opt()} methods return an object which you can cast or query for type.
* There are also typed {@code get} and {@code opt} methods that do type checking and type coersion for you.
* <p/>
* The texts produced by the {@code toString} methods strictly conform to JSON syntax rules. The constructors are
* more forgiving in the texts they will accept:
* <ul>
* <li>An extra {@code ,}&nbsp;<small>(comma)</small> may appear just before the closing bracket.</li>
* <li>The {@code null} value will be inserted when there is {@code ,}&nbsp;<small>(comma)</small> elision.</li>
* <li>Strings may be quoted with {@code '}&nbsp;<small>(single quote)</small>.</li>
* <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not
* contain leading or trailing spaces, and if they do not contain any of these characters:
* {@code { } [ ] / \ : , = ; #} and if they do not look like numbers and if they are not the reserved words
* {@code true}, {@code false}, or {@code null}.</li>
* <li>Values can be separated by {@code ;} <small>(semicolon)</small> as well as by {@code ,}
* <small>(comma)</small>.</li>
* <li>Numbers may have the {@code 0-} <small>(octal)</small> or {@code 0x-} <small>(hex)</small> prefix.</li>
* <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li>
* </ul>
*
* @author JSON.org
* @version 2
*/
@SuppressWarnings("serial")
public final class JSONArray extends JSONCollection implements Iterable<Object> {

    /**
     * The arrayList where the JSONArray's properties are kept.
     */
    private final List<Object> list = new ArrayList<Object>();

    /**
     * Construct an empty JSONArray.
     */
    public JSONArray() {
    }

    public JSONArray(String text) {
        JSONTokener tokener = new JSONTokener(text);

        parse(tokener);
    }

    public JSONArray(Object... values) {
        for (Object value : values)
            put(value);
    }

    /**
     * Create a new array, and adds all values fro the iterable to the array (using {@link #putAll(Iterable)}.
     * <p/>
     * This is implemented as a static method so as not to break the semantics of the existing {@link #JSONArray(Object...)} constructor.
     * Adding a constructor of type Iterable would change the meaning of <code>new JSONArray(new JSONArray())</code>.
     *
     * @param iterable
     *         collection ot value to include, or null
     * @since 5.4
     */
    public static JSONArray from(Iterable<?> iterable) {
        return new JSONArray().putAll(iterable);
    }

    @Override
    public Iterator<Object> iterator() {
        return list.iterator();
    }

    /**
     * Construct a JSONArray from a JSONTokener.
     *
     * @param tokenizer
     *         A JSONTokener
     * @throws RuntimeException
     *         If there is a syntax error.
     */
    JSONArray(JSONTokener tokenizer) {
        assert tokenizer != null;

        parse(tokenizer);
    }

    private void parse(JSONTokener tokenizer) {
        if (tokenizer.nextClean() != '[') {
            throw tokenizer.syntaxError("A JSONArray text must start with '['");
        }

        if (tokenizer.nextClean() == ']') {
            return;
        }

        tokenizer.back();

        while (true) {
            if (tokenizer.nextClean() == ',') {
                tokenizer.back();
                list.add(JSONObject.NULL);
            } else {
                tokenizer.back();
                list.add(tokenizer.nextValue());
            }

            switch (tokenizer.nextClean()) {
            case ';':
            case ',':
                if (tokenizer.nextClean() == ']') {
                    return;
                }
                tokenizer.back();
                break;

            case ']':
                return;

            default:
                throw tokenizer.syntaxError("Expected a ',' or ']'");
            }
        }
    }

    /**
     * Get the object value associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return An object value.
     * @throws RuntimeException
     *         If there is no value for the index.
     */
    public Object get(int index) {
        return list.get(index);
    }

    /**
     * Remove the object associated with the index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return An object removed.
     * @throws RuntimeException
     *         If there is no value for the index.
     */
    public Object remove(int index) {
        return list.remove(index);
    }

    /**
     * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return The truth.
     * @throws RuntimeException
     *         If there is no value for the index or if the value is not convertable to boolean.
     */
    public boolean getBoolean(int index) {
        Object value = get(index);

        if (value instanceof Boolean) {
            return (Boolean) value;
        }

        if (value instanceof String) {
            String asString = (String) value;

            if (asString.equalsIgnoreCase("false"))
                return false;

            if (asString.equalsIgnoreCase("true"))
                return true;
        }

        throw new RuntimeException("JSONArray[" + index + "] is not a Boolean.");
    }

    /**
     * Get the double value associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return The value.
     * @throws IllegalArgumentException
     *         If the key is not found or if the value cannot be converted to a number.
     */
    public double getDouble(int index) {
        Object value = get(index);

        try {
            if (value instanceof Number)
                return ((Number) value).doubleValue();

            return Double.valueOf((String) value);
        } catch (Exception e) {
            throw new IllegalArgumentException("JSONArray[" + index + "] is not a number.");
        }
    }

    /**
     * Get the int value associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return The value.
     * @throws IllegalArgumentException
     *         If the key is not found or if the value cannot be converted to a number. if the
     *         value cannot be converted to a number.
     */
    public int getInt(int index) {
        Object o = get(index);
        return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index);
    }

    /**
     * Get the JSONArray associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return A JSONArray value.
     * @throws RuntimeException
     *         If there is no value for the index. or if the value is not a JSONArray
     */
    public JSONArray getJSONArray(int index) {
        Object o = get(index);
        if (o instanceof JSONArray) {
            return (JSONArray) o;
        }

        throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray.");
    }

    /**
     * Get the JSONObject associated with an index.
     *
     * @param index
     *         subscript
     * @return A JSONObject value.
     * @throws RuntimeException
     *         If there is no value for the index or if the value is not a JSONObject
     */
    public JSONObject getJSONObject(int index) {
        Object o = get(index);
        if (o instanceof JSONObject) {
            return (JSONObject) o;
        }

        throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject.");
    }

    /**
     * Get the long value associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return The value.
     * @throws IllegalArgumentException
     *         If the key is not found or if the value cannot be converted to a number.
     */
    public long getLong(int index) {
        Object o = get(index);
        return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index);
    }

    /**
     * Get the string associated with an index.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return A string value.
     * @throws RuntimeException
     *         If there is no value for the index.
     */
    public String getString(int index) {
        return get(index).toString();
    }

    /**
     * Determine if the value is null.
     *
     * @param index
     *         The index must be between 0 and length() - 1.
     * @return true if the value at the index is null, or if there is no value.
     */
    public boolean isNull(int index) {
        return get(index) == JSONObject.NULL;
    }

    /**
     * Get the number of elements in the JSONArray, included nulls.
     *
     * @return The length (or size).
     */
    public int length() {
        return list.size();
    }

    /**
     * Append an object value. This increases the array's length by one.
     *
     * @param value
     *         An object value. The value should be a Boolean, Double, Integer, JSONArray, JSONObject, JSONLiteral,
     *         Long, or String, or the JSONObject.NULL singleton.
     * @return this array
     */
    public JSONArray put(Object value) {
        assert value != null;

        JSONObject.testValidity(value);

        list.add(value);

        return this;
    }

    /**
     * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then
     * null elements will be added as necessary to pad it out.
     *
     * @param index
     *         The subscript.
     * @param value
     *         The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray,
     *         JSONObject, JSONString, Long, or String, or the JSONObject.NULL singeton.
     * @return this array
     * @throws RuntimeException
     *         If the index is negative or if the the value is an invalid number.
     */
    public JSONArray put(int index, Object value) {
        assert value != null;

        if (index < 0) {
            throw new RuntimeException("JSONArray[" + index + "] not found.");
        }

        JSONObject.testValidity(value);

        if (index < length()) {
            list.set(index, value);
        } else {
            while (index != length())
                list.add(JSONObject.NULL);

            list.add(value);
        }

        return this;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;

        if (!(obj instanceof JSONArray))
            return false;

        JSONArray other = (JSONArray) obj;

        return list.equals(other.list);
    }

    @Override
    void print(JSONPrintSession session) {
        session.printSymbol('[');

        session.indent();

        boolean comma = false;

        for (Object value : list) {
            if (comma)
                session.printSymbol(',');

            session.newline();

            JSONObject.printValue(session, value);

            comma = true;
        }

        session.outdent();

        if (comma)
            session.newline();

        session.printSymbol(']');
    }

    /**
     * Puts all objects from the collection into this JSONArray, using {@link #put(Object)}.
     *
     * @param collection
     *         List, array, JSONArray, or other iterable object, or null
     * @return this JSONArray
     * @since 5.4
     */
    public JSONArray putAll(Iterable<?> collection) {
        if (collection != null) {
            for (Object o : collection) {
                put(o);
            }
        }

        return this;
    }

    /**
     * Returns an unmodifiable list of the contents of the array. This is a wrapper around the list's internal
     * storage and is live (changes to the JSONArray affect the returned List).
     *
     * @return unmodifiable list of array contents
     * @since 5.4
     */
    public List<Object> toList() {
        return Collections.unmodifiableList(list);
    }
}
